package com.jxpanda.spring.module.auth.core.authentication.manager;


import com.jxpanda.infrastructure.core.tollkit.ReflectionKit;
import com.jxpanda.infrastructure.spring.toolkit.SpringContextKit;
import com.jxpanda.spring.module.auth.core.authentication.token.CollaborativeAuthenticationToken;
import com.jxpanda.spring.module.auth.core.authentication.token.UserDetailsAuthenticationToken;
import com.jxpanda.spring.module.auth.core.register.strategy.OAuth2ReactiveRegisterStrategyResolver;
import com.jxpanda.spring.module.auth.core.user.AbstractOAuth2User;
import com.jxpanda.spring.module.auth.core.user.OAuth2ReactiveUserDetailsRepository;
import com.jxpanda.spring.module.auth.core.user.OAuth2UserContext;
import com.jxpanda.spring.module.auth.endpoint.OAuth2Request;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.user.OAuth2User;
import reactor.core.publisher.Mono;

import java.util.Objects;

public class OAuth2UserDetailsRepositoryReactiveAuthenticationManager extends AbstractUserDetailsRepositoryReactiveAuthenticationManager {


    public OAuth2UserDetailsRepositoryReactiveAuthenticationManager(OAuth2ReactiveUserDetailsRepository oAuth2ReactiveUserDetailsRepository) {
        super(oAuth2ReactiveUserDetailsRepository);
    }

    private OAuth2User getOauth2User(Authentication authentication) {
        return ReflectionKit.cast(authentication.getPrincipal());
    }

    private String getOauth2UserIdAttributeName(ClientRegistration clientRegistration, OAuth2User oAuth2User) {
        // TODO: 这里似乎需要考虑其他获取方式，因为provider并不是强制设置的。
        Object attributeInOauth2User = oAuth2User.getAttribute(AbstractOAuth2User.USER_ID_ATTRIBUTE_NAME);
        if (attributeInOauth2User != null) {
            return attributeInOauth2User.toString();
        }
        String attributeInConfig = clientRegistration.getProviderDetails()
                .getUserInfoEndpoint()
                .getUserNameAttributeName();
        return Objects.requireNonNullElse(attributeInConfig, "");
    }

    private OAuth2ReactiveRegisterStrategyResolver oAuth2ReactiveRegisterStrategyResolver() {
        return SpringContextKit.getBeanOrDefault(OAuth2ReactiveRegisterStrategyResolver.class, OAuth2ReactiveRegisterStrategyResolver.emptyResolver());
    }

    private OAuth2UserContext buildOAuth2UserContext(CollaborativeAuthenticationToken authentication) {
        ClientRegistration clientRegistration = authentication.getClientRegistration();
        OAuth2User oauth2User = getOauth2User(authentication);
        String oauth2UserIdAttributeName = getOauth2UserIdAttributeName(clientRegistration, oauth2User);
        OAuth2Request oAuth2Request = authentication.getOAuth2Request();
        return OAuth2UserContext.builder()
                // Spring 的DefaultOauth2User对象是把对应的标识符设置到了name属性中，所以这里使用getName()获取
                // 为了兼容这个逻辑，后续的自定义OAuth2User子类也要把有效的标识符设置到这个属性中
                .oauth2UserId(oauth2User.getName())
                .oauth2UserIdAttributeName(oauth2UserIdAttributeName)
                .oAuth2User(oauth2User)
                .registrationId(clientRegistration.getRegistrationId())
                .grantType(oAuth2Request.getGrantType())
                .clientId(oAuth2Request.getClientId())
                .scope(oAuth2Request.getScope())
                .additionalParameters(oAuth2Request.getAdditionalParameters())
                .build();
    }

    @Override
    public Mono<CollaborativeAuthenticationToken> authenticate(CollaborativeAuthenticationToken authentication) {
        OAuth2UserContext oAuth2UserContext = buildOAuth2UserContext(authentication);
        return oAuth2ReactiveUserDetailsRepository()
                // 加载用户
                .loadUser(oAuth2UserContext)
                // 防御性编程，原则上是不会返回null的对象的，会返回Mono.empty()才对
                .filter(Objects::nonNull)
                // 如果没有获取到用户，则跟怒注册策略进行注册行为
                // 这里要使用Mono.defer来确保获取用户行为在注册行为之前执行
                // 因为resolve函数内部是不能保证惰性的，根据策略的不同可能是一个非惰性的结果。
                .switchIfEmpty(Mono.defer(() -> oAuth2ReactiveRegisterStrategyResolver().resolve(oAuth2UserContext)))
                // 不论是加载成功成功还是注册成功，都封装一个UserDetailsAuthenticationToken返回
                // 如果注册失败，一般是返回异常，由外部捕获处理
                .map(userDetails -> new UserDetailsAuthenticationToken<>(authentication.getOAuth2Request(), authentication.getClientRegistration(), userDetails));
    }

}
